/*-------------------------------------------------------------------------------------------------------------------------
 * GLUT based Cube Game main code.
 *
 * draws any cube, 2x2x2, 3x3x3, ... and rotates by Archball logic
 * and each cube wings rotates by dragging.
 *
 * this can be builded linux and Windows.
 *
 * this code is written based Bruce's GLUT OpenGL Basecode
 *
 *  Specially made for Nehe's Gamedev Website
 *            http://nehe.gamedev.net
 *            April 2003
 *
 * TODO: 1. identify rotation variables, 2. split glut logic and opengl logic in this codes.
 * @author soonoh.jung@gmail.com
 * @since  April 2009
 *---------------------------------------------------------------------------------------------------------------------------*/

#include <wchar.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <GL/glut.h>

#ifdef WIN32
#include <windows.h>
#include "shared/gettimeofday.h"
#endif

#include "frames.h"
#include "ArcBall.h"
#include "cube.h"

#define ONE_SECOND      1
//#define DRAW_ARROW        // draw arrow or not.

/* creates a enum type for mouse buttons */
enum {
    BUTTON_LEFT = 0, BUTTON_RIGHT, BUTTON_LEFT_TRANSLATE,
};

/* set global variables */
int mButton = -1;
float mOldY, mOldX;

/* vectors that makes the rotation and translation of the cube */
float eye[3] = { 0.0f, 0.0f, 20.0f };

const int ESC = 27;

int wireframe = 0;

/* windows size and position constants */
const int GL_WIN_WIDTH = 640;
const int GL_WIN_HEIGHT = 480;
const int GL_WIN_INITIAL_X = 0;
const int GL_WIN_INITIAL_Y = 0;

float rotate_speed = 1.8f;
int g_playTime = 0.0f;
struct timeval g_startTime;

Matrix4fT Transform = {	// final transform.
		1.0f, 0.0f, 0.0f, 0.0f,
		0.0f, 1.0f, 0.0f, 0.0f,
		0.0f, 0.0f, 1.0f, 0.0f,
		0.0f, 0.0f, 0.0f, 1.0f
};

Matrix3fT LastRot = {	// last rotation.
		1.0f, 0.0f, 0.0f,
		0.0f, 1.0f, 0.0f,
		0.0f, 0.0f, 1.0f
};

Matrix3fT ThisRot = {
		1.0f, 0.0f, 0.0f,
		0.0f, 1.0f, 0.0f,
		0.0f, 0.0f, 1.0f
};

ArcBallT ArcBall((float)GL_WIN_WIDTH, (float)GL_WIN_HEIGHT);
Point2fT MousePt;
bool isClicked = false;
bool isRClicked = false;
bool isDragging = false;

GLfloat g_viewPortRatio = 0.0f;//viewport ratio width/height
/*
 * -1 : not selected  each cube has 6 face names, the selected cube names.
 * 1000 ~ 1003 is used for arrow, but currently arrow is not displayed.
 */
GLint g_selectedCubeIndex = -1;
// selected cube face.
int g_selected_cube_face = 0;

int g_arrowOperations[4][3] = { { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 } };
// a message for debugging to be displayed on the screen.
static char x_debugMessage[50] = { 0 };

GLfloat rquad = 0.0f; // Angle For The Quad

/* Holds X, Y, Z axis rotation ratio used for cube rotation. */
float g_rotateAxises[3] = { 0.0f, 0.0f, 0.0f };

bool g_isRotated = false;
bool g_cube_selected = false;

BigCube* g_bigCube = new BigCube();
CubeRotateOperation* currentRotateOperation;

// absolute axis vectors in perspective of user.
const GLfloat X_AXIS_VECTOR[] = { 1.0f, 0.0f, 0.0f };
const GLfloat Y_AXIS_VECTOR[] = { 0.0f, 1.0f, 0.0f };
const GLfloat Z_AXIS_VECTOR[] = { 0.0f, 0.0f, 1.0f };

GLfloat gltVectorDotProduct(const GLfloat vU[], const GLfloat vV[]);
GLfloat absf(GLfloat f);

/**
 * get length of vector
 */
GLfloat vectorMagnitude(GLfloat vector[]) {
    return sqrt((vector[0] * vector[0]) + (vector[1] * vector[1]) + (vector[2] * vector[2]));
}
/**
 * add rotation.
 * @param newRotateDirection a direction 1 or -1
 * @param newColumnIndex a column index for rotation axis  0,1,2,...
 * @param newRotateAxis a base axis of rotation, x,y,z axises are 0,1,2.
 */
void setRotation(int newRotateDirection, int newColumnIndex, int newRotateAxis) {

    CubeRotateOperation* operation = new CubeRotateOperation(newRotateAxis,
            newColumnIndex, newRotateDirection);
    cubeRotateOperationStack.push(operation);
}

void performRotation() {
    rquad = 0.0f;
    g_isRotated = true;
    delete currentRotateOperation;
    currentRotateOperation = cubeRotateOperationStack.top();
    cubeRotateOperationStack.pop();
}
void startRotation(int newRotateDirection, int newColumnIndex,
        int newRotateAxis) {
    setRotation(newRotateDirection, newColumnIndex, newRotateAxis);
    performRotation();
}

void drawFrameRate(char *str, void *font, GLclampf r, GLclampf g, GLclampf b,
        GLfloat x, GLfloat y);

void glutDisplay(void);

//------------------------------------------------------------------------
// Calculates the current frame rate
//------------------------------------------------------------------------
void CalculateFrameRate() {

    static float framesPerSecond = 0.0f; // This will store our fps
    static long lastTime = 0; // This will hold the time from the last frame
    static char strFrameRate[50] = { 0 }; // We will store the string here for the window title

    // struct for the time value
    struct timeval currentTime;
    currentTime.tv_sec = 0;
    currentTime.tv_usec = 0;

    // gets the microseconds passed since app started
    gettimeofday(&currentTime, NULL);

    // Increase the frame counter
    ++framesPerSecond;

    if (currentTime.tv_sec - lastTime >= ONE_SECOND) {
        lastTime = currentTime.tv_sec;

        // Copy the frames per second into a string to display in the window
        sprintf(strFrameRate, "Current Frames Per Second: %d",
                int(framesPerSecond));

        // Reset the frames per second
        framesPerSecond = 0;

    }

    // draw frame rate on screen
    drawFrameRate(strFrameRate, GLUT_BITMAP_HELVETICA_10, 1.0f, 1.0f, 1.0f,
            0.05, 0.95);
}

//------------------------------------------------------------------------
// Draws the current frame rate on screen
//------------------------------------------------------------------------
void drawFrameRate(char *str, void *font, GLclampf r, GLclampf g, GLclampf b,
        GLfloat x, GLfloat y) {
    /* font: font to use, e.g., GLUT_BITMAP_HELVETICA_10
     r, g, b: text colour
     x, y: text position in window: range [0,0] (bottom left of window)
     to [1,1] (top right of window). */

    char *ch;
    GLint matrixMode;
    GLboolean lightingOn;

    lightingOn = glIsEnabled(GL_LIGHTING); /* lighting on? */
    if (lightingOn)
        glDisable(GL_LIGHTING);

    glGetIntegerv(GL_MATRIX_MODE, &matrixMode); /* matrix mode? */

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    gluOrtho2D(0.0, 1.0, 0.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    glPushAttrib(GL_COLOR_BUFFER_BIT); /* save current colour */
    glColor3f(r, g, b);
    glRasterPos3f(x, y, 0.0);
    for (ch = str; *ch; ch++) {
        glutBitmapCharacter(font, (int) *ch);
    }
    glPopAttrib();
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(matrixMode);
    if (lightingOn)
        glEnable(GL_LIGHTING);
}

void timerText(char* message) {
    float x = 0.05f;
    float y = 0.4f;

    drawFrameRate(message, GLUT_BITMAP_HELVETICA_10, 1.0f, 1.0f, 1.0f, x, y);
}

void debugMessage(char* message) {
    float x = 0.05f;
    float y = 0.7f;

    drawFrameRate(message, GLUT_BITMAP_HELVETICA_10, 1.0f, 1.0f, 1.0f, x, y);
}

void ProcessSelect(GLuint index, int depth, int hits) {
    g_selectedCubeIndex = index;
    if (index < 1000 || index > 1003)
        return;
    int arrowOperationIndex = index - 1000;
    startRotation(g_arrowOperations[arrowOperationIndex][0],
            g_arrowOperations[arrowOperationIndex][1],
            g_arrowOperations[arrowOperationIndex][2]);
}

/**
 * Pick object.
 * referenced from: http://www.gisdeveloper.co.kr/opengl_tutorials/opengl13/13.htm
 *
 * @param x mouse x.
 * @param y mouse y.
 */
void SelectObjects(GLint x, GLint y) {
    GLuint buffer[64];
    GLint hits, viewport[4];

    glSelectBuffer(64, buffer);
    glGetIntegerv(GL_VIEWPORT, viewport);

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    // Puts OpenGL In Selection Mode. Nothing Will Be Drawn. Object ID's and Extents Are Stored In The Buffer.
    glRenderMode(GL_SELECT);
    glLoadIdentity();
    gluPickMatrix(x, viewport[3] - y, 1, 1, viewport);
    gluPerspective(45.0f, g_viewPortRatio, 0.1f, 100.0f);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glutDisplay();
    hits = glRenderMode(GL_RENDER);
    if (hits > 0) { // If There Were More Than 0 Hits

        int choose = buffer[3]; // Make Our Selection The First Object
        int depth = buffer[1]; // Store How Far Away It Is

        for (int loop = 1; loop < hits; loop++) // Loop Through All The Detected Hits
        {
            // If This Object Is Closer To Us Than The One We Have Selected
            if (buffer[loop * 4 + 1] < GLuint(depth)) {
                if (buffer[loop * 4 + 1] != 2147482496 && buffer[loop * 4 + 3]
                        > 0 && buffer[loop * 4 + 3] < 1004) {
                    choose = buffer[loop * 4 + 3]; // Select The Closer Object
                    depth = buffer[loop * 4 + 1]; // Store How Far Away It Is
                }
            }
        }

        ProcessSelect(choose, depth, buffer[0]);
    } else {
        g_selectedCubeIndex = -1;
    }

    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
}

//------------------------------------------------------------------------
// Window resize function
//------------------------------------------------------------------------
void glutResize(int width, int height) {
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    g_viewPortRatio = (GLfloat) width / (GLfloat) height;
    /* modify this line to change perspective values */
    gluPerspective(45.0, g_viewPortRatio, 1.0, 300.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    ArcBall.setBounds((GLfloat) width, (GLfloat) height); //*NEW* Update mouse bounds for arcball
}

//------------------------------------------------------------------------
// Function that handles keyboard inputs
//------------------------------------------------------------------------
void glutKeyboard(unsigned char key, int x, int y) {
    GLint viewport[4];
    glGetIntegerv(GL_VIEWPORT, viewport); // <4>

    switch (key) {
    case ESC:
        exit(0);
    case 'W':
        wireframe = !wireframe;
        break;
        // this is Cube rotation characters used in formally,
		// but I made it simple to use as only one char.
    case 'f':
    case 'F':
    case 'l':
    case 'L': 
    case 'u':
    case 'U':
    case 'd':
    case 'D':
    case 'r':
    case 'R':
    case 'b':
    case 'B':
        cubeRotateOperationStack.push(new CubeRotateOperation(key));
        performRotation();
        break;
    case 's':// suffle cubes.
        CubeRotateOperation::initRandom();
        for (int i = 0; i < 20; ++i) {
            cubeRotateOperationStack.push(CubeRotateOperation::random());
        }
        performRotation();
        g_playTime = -1;
        break;
    case '1':
        F2L_index++;
        if (F2L_index >= F2L_SIZE) {
            F2L_index = 0;
        }
        break;
    case '3':
        F2L_index--;
        if (F2L_index <= 0) {
            F2L_index = 0;
        }
        break;
    case '4':
        srand(time(NULL));
        F2L_index = rand() % F2L_SIZE;
        break;
    case '5': {
        string::reverse_iterator it = F2L[F2L_index].rbegin();
        while (it != F2L[F2L_index].rend()) {
            cout << *it;
            cubeRotateOperationStack.push(new CubeRotateOperation(*it));
            it++;
        }
        performRotation();
    }
        break;

    case '2': {
        string::iterator it = F2L[F2L_index].begin();
        while (it != F2L[F2L_index].end()) {
            cout << *it;
            cubeRotateOperationStack.push(new CubeRotateOperation(*it, true));
            it++;
        }
        performRotation();
    }
        break;
    case '-':
        rotate_speed -= 0.1f;
        break;
    case '=':
        /* same as '+' */
    case '+':
        rotate_speed += 0.1f;
		break;
	default:
		/* do nothing */
        break;
    }
}

/**
 * do motion effect to rotate cubes.
 */
void doMotionEffect(GLfloat motionVector[]) {
    GLfloat length = vectorMagnitude(motionVector);
	// if length of vector too small to be aware of users motion,
	// ignore.
    if (length < 10.0f) {
        return;
    }

	// x, y and z axis vectors current.
    const GLfloat vx[] = { ThisRot.s.XX, ThisRot.s.XY, ThisRot.s.XZ };
    const GLfloat vy[] = { ThisRot.s.YX, ThisRot.s.YY, ThisRot.s.YZ };
    const GLfloat vz[] = { ThisRot.s.ZX, ThisRot.s.ZY, ThisRot.s.ZZ };

	// get dot product of motion vector for each axis vector.
    GLfloat xDot;
    xDot = gltVectorDotProduct(motionVector, vx);
    GLfloat yDot;
    yDot = gltVectorDotProduct(motionVector, vy);
    GLfloat zDot;
    zDot = gltVectorDotProduct(motionVector, vz);
    int arrowIndex = -1;
    float xDotAbs;
    xDotAbs = absf(xDot);
    float yDotAbs;
    yDotAbs = absf(yDot);
    float zDotAbs;
    zDotAbs = absf(zDot);

	// find arrow(direction) with selected face and dot products.
    switch (g_selected_cube_face) {
    case CUBE_FACE_FRONT:
    case CUBE_FACE_BACK:
        if (xDotAbs > yDotAbs) {
            if (xDot > 0)
                arrowIndex = 1;
            else
                arrowIndex = 3;
        } else {
            if (yDot > 0)
                arrowIndex = 0;
            else
                arrowIndex = 2;
        }
        break;
    case CUBE_FACE_LEFT:
    case CUBE_FACE_RIGHT:
        if (yDotAbs > zDotAbs) {
            if (yDot > 0)
                arrowIndex = 0;
            else
                arrowIndex = 2;
        } else {
            if (zDot > 0)
                arrowIndex = 1;
            else
                arrowIndex = 3;
        }
        break;
    case CUBE_FACE_TOP:
    case CUBE_FACE_BOTTOM:
        if (zDotAbs > xDotAbs) {
            if (zDot > 0)
                arrowIndex = 0;
            else
                arrowIndex = 2;
        } else {
            if (xDot > 0)
                arrowIndex = 1;
            else
                arrowIndex = 3;
        }
        break;
    }
    if (arrowIndex == -1)
        return;
    isDragging = false;
    isClicked = false;
    ProcessSelect(1000 + arrowIndex, 0, 0);
}

//------------------------------------------------------------------------
// Moves the screen based on mouse pressed button
//------------------------------------------------------------------------
void glutMotion(int x, int y) {
    MousePt.s.X = x;
    MousePt.s.Y = y;

    if (mButton == BUTTON_LEFT) {
        if (g_cube_selected) {
        	// note that y coordinate is reversed by contrast with x coordinate.
            GLfloat motionVector[] = { x - mOldX, mOldY - y, 0.0f };
            doMotionEffect(motionVector);
        } else {
            isClicked = true;
        }
    } else if (mButton == BUTTON_RIGHT) {
        /*
         translate the screen, z axis
         gives the idea of zooming in and out
         */
        eye[2] -= (mOldY - y) * 0.05f; // here I multiply by a 0.2 factor to
        // slow down the zoom
        isRClicked = true;
    } else if (mButton == BUTTON_LEFT_TRANSLATE) {
        eye[0] += (mOldX - x) * 0.01f;
        eye[1] -= (mOldY - y) * 0.01f;
    }
}

//------------------------------------------------------------------------
// Function that handles mouse input
//------------------------------------------------------------------------
void glutMouse(int button, int state, int x, int y) {
    if (state == GLUT_DOWN) {
        mOldX = x;
        mOldY = y;
        switch (button) {
        case GLUT_LEFT_BUTTON:
            // check selection.
            SelectObjects(x, y);
            if (glutGetModifiers() == GLUT_ACTIVE_CTRL) {
                mButton = BUTTON_LEFT_TRANSLATE;
                break;
            } else {
                mButton = BUTTON_LEFT;
                break;
            }
        case GLUT_RIGHT_BUTTON:
            mButton = BUTTON_RIGHT;
            break;
        }
    } else if (state == GLUT_UP) {
        mButton = -1;
        switch (button) {
        case GLUT_LEFT_BUTTON:
            isClicked = false;
            break;
        case GLUT_RIGHT_BUTTON:
            isRClicked = false;
            break;
        }
    }
}

//------------------------------------------------------------------------
// The glut's menu callback function. Called every time an option is selected
//------------------------------------------------------------------------
void glutMenu(int value) {
    switch (value) {
    case 1:
        glutFullScreen();
        return;

    case 2:
        exit(0);
    }
}

//------------------------------------------------------------------------
// Function that handles special keyboards input (SHIFT, CTRL, ALT)
//------------------------------------------------------------------------
void glutSpecial(int value, int x, int y) {
    switch (value) {
    case GLUT_KEY_F1:
        glutFullScreen();
        return;
    }
}

float g_cubeColor[8][3] = {
	{ 0.0f, 1.0f, 0.0f },
	{ 1.0f, 0.2f, 0.2f },
	{ 0.2f, 0.2f, 1.0f },
	{ 1.0f, 0.6f, 0.0f },
	{ 1.0f, 1.0f, 0.0f },
	{ 1.0f, 1.0f, 1.0f },
	
	{ 0.0f, 0.0f, 0.0f },
	{ 0.5f, 0.0f, 0.5f } };

#ifdef DRAW_ARROW
const float ARROW_TOP_SIZE = 2.5f;
const float ARROW_BOTTOM_SIZE = 1.1f;
#endif

void drawArrow(int face, int x, int y, int z) {
    glColor3f(g_cubeColor[7][0], g_cubeColor[7][1], g_cubeColor[7][2]);
    g_selected_cube_face = face;
    glDisable(GL_CULL_FACE);
    switch (face) {
    case CUBE_FACE_FRONT:
#ifdef DRAW_ARROW
        glPushName(1000);
        glBegin(GL_TRIANGLES);
        glVertex3f( 0.0f, ARROW_TOP_SIZE, ARROW_BOTTOM_SIZE); // Top
        glVertex3f(-ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Left
        glVertex3f( ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glEnd();
        glPopName();

        glPushName(1001);
        glBegin(GL_TRIANGLES);
        glVertex3f( ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(ARROW_TOP_SIZE, 0.0f, ARROW_BOTTOM_SIZE); // Bottom Left
        glEnd();
        glPopName();

        glPushName(1002);
        glBegin(GL_TRIANGLES);
        glVertex3f(-ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(0.0f, -ARROW_TOP_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glEnd();
        glPopName();

        glPushName(1003);
        glBegin(GL_TRIANGLES);
        glVertex3f(-ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(-ARROW_TOP_SIZE, 0.0f, ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(-ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glEnd();
        glPopName();
#endif

        g_arrowOperations[0][0] = -1;
        g_arrowOperations[0][1] = x;
        g_arrowOperations[0][2] = 0;
        g_arrowOperations[1][0] = 1;
        g_arrowOperations[1][1] = y;
        g_arrowOperations[1][2] = 1;
        g_arrowOperations[2][0] = 1;
        g_arrowOperations[2][1] = x;
        g_arrowOperations[2][2] = 0;
        g_arrowOperations[3][0] = -1;
        g_arrowOperations[3][1] = y;
        g_arrowOperations[3][2] = 1;
        break;
    case CUBE_FACE_LEFT:
#ifdef DRAW_ARROW
        glPushName(1000);
        glBegin(GL_TRIANGLES);
        glVertex3f(-ARROW_BOTTOM_SIZE, ARROW_TOP_SIZE, 0.0f); // Top
        glVertex3f(-ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Left
        glVertex3f(-ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glEnd();
        glPopName();

        glPushName(1001);
        glBegin(GL_TRIANGLES);
        glVertex3f(-ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(-ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(-ARROW_BOTTOM_SIZE, 0.0f, ARROW_TOP_SIZE); // Bottom Left
        glEnd();
        glPopName();

        glPushName(1002);
        glBegin(GL_TRIANGLES);
        glVertex3f(-ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(-ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(-ARROW_BOTTOM_SIZE, -ARROW_TOP_SIZE, 0.0f); // Bottom Right
        glEnd();
        glPopName();

        glPushName(1003);
        glBegin(GL_TRIANGLES);
        glVertex3f(-ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(-ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(-ARROW_BOTTOM_SIZE, 0.0f, -ARROW_TOP_SIZE); // Bottom Right
        glEnd();
        glPopName();
#endif

        g_arrowOperations[0][0] = -1;
        g_arrowOperations[0][1] = z;
        g_arrowOperations[0][2] = 2;
        g_arrowOperations[1][0] = 1;
        g_arrowOperations[1][1] = y;
        g_arrowOperations[1][2] = 1;
        g_arrowOperations[2][0] = 1;
        g_arrowOperations[2][1] = z;
        g_arrowOperations[2][2] = 2;
        g_arrowOperations[3][0] = -1;
        g_arrowOperations[3][1] = y;
        g_arrowOperations[3][2] = 1;
        break;
    case CUBE_FACE_BACK:
#ifdef DRAW_ARROW
        glPushName(1000);
        glBegin(GL_TRIANGLES);
        glVertex3f( 0.0f, ARROW_TOP_SIZE, -ARROW_BOTTOM_SIZE); // Top
        glVertex3f(-ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Left
        glVertex3f( ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glEnd();
        glPopName();

        glPushName(1001);
        glBegin(GL_TRIANGLES);
        glVertex3f( ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(ARROW_TOP_SIZE, 0.0f, -ARROW_BOTTOM_SIZE); // Bottom Left
        glVertex3f(ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glEnd();
        glPopName();

        glPushName(1002);
        glBegin(GL_TRIANGLES);
        glVertex3f(ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(0.0f, -ARROW_TOP_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(-ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glEnd();
        glPopName();

        glPushName(1003);
        glBegin(GL_TRIANGLES);
        glVertex3f(-ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(-ARROW_TOP_SIZE, 0.0f, -ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(-ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glEnd();
        glPopName();
#endif

        g_arrowOperations[0][0] = 1;
        g_arrowOperations[0][1] = x;
        g_arrowOperations[0][2] = 0;
        g_arrowOperations[1][0] = -1;
        g_arrowOperations[1][1] = y;
        g_arrowOperations[1][2] = 1;
        g_arrowOperations[2][0] = -1;
        g_arrowOperations[2][1] = x;
        g_arrowOperations[2][2] = 0;
        g_arrowOperations[3][0] = 1;
        g_arrowOperations[3][1] = y;
        g_arrowOperations[3][2] = 1;
        break;
    case CUBE_FACE_RIGHT:
#ifdef DRAW_ARROW
        glPushName(1000);
        glBegin(GL_TRIANGLES);
        glVertex3f(ARROW_BOTTOM_SIZE, ARROW_TOP_SIZE, 0.0f); // Top
        glVertex3f(ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Left
        glVertex3f(ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glEnd();
        glPopName();

        glPushName(1001);
        glBegin(GL_TRIANGLES);
        glVertex3f(ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(ARROW_BOTTOM_SIZE, 0.0f, ARROW_TOP_SIZE); // Bottom Left
        glVertex3f(ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glEnd();
        glPopName();

        glPushName(1002);
        glBegin(GL_TRIANGLES);
        glVertex3f(ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(ARROW_BOTTOM_SIZE, -ARROW_TOP_SIZE, 0.0f); // Bottom Right
        glVertex3f(ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right

        glEnd();
        glPopName();

        glPushName(1003);
        glBegin(GL_TRIANGLES);
        glVertex3f(ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(ARROW_BOTTOM_SIZE, 0.0f, -ARROW_TOP_SIZE); // Bottom Right
        glVertex3f(ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glEnd();
        glPopName();
#endif

        g_arrowOperations[0][0] = 1;
        g_arrowOperations[0][1] = z;
        g_arrowOperations[0][2] = 2;
        g_arrowOperations[1][0] = -1;
        g_arrowOperations[1][1] = y;
        g_arrowOperations[1][2] = 1;
        g_arrowOperations[2][0] = -1;
        g_arrowOperations[2][1] = z;
        g_arrowOperations[2][2] = 2;
        g_arrowOperations[3][0] = 1;
        g_arrowOperations[3][1] = y;
        g_arrowOperations[3][2] = 1;
        break;
    case CUBE_FACE_TOP:
#ifdef DRAW_ARROW
        glPushName(1000);
        glBegin(GL_TRIANGLES);
        glVertex3f( 0.0f, ARROW_BOTTOM_SIZE, ARROW_TOP_SIZE); // Top
        glVertex3f(-ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Left
        glVertex3f( ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glEnd();
        glPopName();

        glPushName(1001);
        glBegin(GL_TRIANGLES);
        glVertex3f( ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(ARROW_TOP_SIZE, ARROW_BOTTOM_SIZE, 0.0f); // Bottom Left
        glVertex3f(ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glEnd();
        glPopName();

        glPushName(1002);
        glBegin(GL_TRIANGLES);
        glVertex3f(ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(0.0f, ARROW_BOTTOM_SIZE, -ARROW_TOP_SIZE); // Bottom Right
        glVertex3f(-ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glEnd();
        glPopName();

        glPushName(1003);
        glBegin(GL_TRIANGLES);
        glVertex3f(-ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(-ARROW_TOP_SIZE, ARROW_BOTTOM_SIZE, 0.0f); // Bottom Right
        glVertex3f(-ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glEnd();
        glPopName();
#endif

        g_arrowOperations[0][0] = 1;
        g_arrowOperations[0][1] = x;
        g_arrowOperations[0][2] = 0;
        g_arrowOperations[1][0] = -1;
        g_arrowOperations[1][1] = z;
        g_arrowOperations[1][2] = 2;
        g_arrowOperations[2][0] = -1;
        g_arrowOperations[2][1] = x;
        g_arrowOperations[2][2] = 0;
        g_arrowOperations[3][0] = 1;
        g_arrowOperations[3][1] = z;
        g_arrowOperations[3][2] = 2;
        break;
    case CUBE_FACE_BOTTOM:
#ifdef DRAW_ARROW
        glPushName(1000);
        glBegin(GL_TRIANGLES);
        glVertex3f( 0.0f, -ARROW_BOTTOM_SIZE, ARROW_TOP_SIZE); // Top
        glVertex3f(-ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Left
        glVertex3f( ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glEnd();
        glPopName();

        glPushName(1001);
        glBegin(GL_TRIANGLES);
        glVertex3f( ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(ARROW_TOP_SIZE, -ARROW_BOTTOM_SIZE, 0.0f); // Bottom Left
        glVertex3f(ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glEnd();
        glPopName();

        glPushName(1002);
        glBegin(GL_TRIANGLES);
        glVertex3f(ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(0.0f, -ARROW_BOTTOM_SIZE, -ARROW_TOP_SIZE); // Bottom Right
        glVertex3f(-ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glEnd();
        glPopName();

        glPushName(1003);
        glBegin(GL_TRIANGLES);
        glVertex3f(-ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE); // Bottom Right
        glVertex3f(-ARROW_TOP_SIZE, -ARROW_BOTTOM_SIZE, 0.0f); // Bottom Right
        glVertex3f(-ARROW_BOTTOM_SIZE, -ARROW_BOTTOM_SIZE, ARROW_BOTTOM_SIZE); // Bottom Right
        glEnd();
        glPopName();
#endif

        g_arrowOperations[0][0] = -1;
        g_arrowOperations[0][1] = x;
        g_arrowOperations[0][2] = 0;
        g_arrowOperations[1][0] = 1;
        g_arrowOperations[1][1] = z;
        g_arrowOperations[1][2] = 2;
        g_arrowOperations[2][0] = 1;
        g_arrowOperations[2][1] = x;
        g_arrowOperations[2][2] = 0;
        g_arrowOperations[3][0] = -1;
        g_arrowOperations[3][1] = z;
        g_arrowOperations[3][2] = 2;
        break;

    default:
        break;
    }
    glEnable(GL_CULL_FACE);
}

void drawOneCube(Cube* cube, int nameId) {

    int* currentCubeColors = cube->getCubeColorArray();

    int i = *currentCubeColors;
    glPushName(0 + nameId);
    glBegin(GL_TRIANGLE_STRIP);
    glNormal3f(0.0f, 0.0f, 1.0f);
    glColor3f(g_cubeColor[i][0], g_cubeColor[i][1], g_cubeColor[i][2]);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glEnd();
    glBegin(GL_LINE_STRIP);
    glColor3f(g_cubeColor[7][0], g_cubeColor[7][1], g_cubeColor[7][2]);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glEnd();
    glPopName();
    glPushName(1 + nameId);

    // Left Face, magenta
    ++currentCubeColors;
    i = *currentCubeColors;
    glBegin(GL_TRIANGLE_STRIP);
    glNormal3f(-1.0f, 0.0f, 0.0f);
    glColor3f(g_cubeColor[i][0], g_cubeColor[i][1], g_cubeColor[i][2]);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glEnd();
    glBegin(GL_LINE_STRIP);
    glColor3f(g_cubeColor[7][0], g_cubeColor[7][1], g_cubeColor[7][2]);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glEnd();
    glPopName();
    glPushName(2 + nameId);

    // Back Face, green
    ++currentCubeColors;
    i = *currentCubeColors;
    glBegin(GL_TRIANGLE_STRIP);
    glNormal3f(0.0f, 0.0f, -1.0f);
    glColor3f(g_cubeColor[i][0], g_cubeColor[i][1], g_cubeColor[i][2]);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glVertex3f(1.0f, 1.0f, -1.0f);
    glEnd();
    glBegin(GL_LINE_STRIP);
    glColor3f(g_cubeColor[7][0], g_cubeColor[7][1], g_cubeColor[7][2]);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glVertex3f(1.0f, 1.0f, -1.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glEnd();
    glPopName();
    glPushName(3 + nameId);

    // Right face, cyan
    ++currentCubeColors;
    i = *currentCubeColors;
    glBegin(GL_TRIANGLE_STRIP);
    glNormal3f(1.0f, 0.0f, 0.0f);
    glColor3f(g_cubeColor[i][0], g_cubeColor[i][1], g_cubeColor[i][2]);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glVertex3f(1.0f, 1.0f, -1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glEnd();
    glBegin(GL_LINE_STRIP);
    glColor3f(g_cubeColor[7][0], g_cubeColor[7][1], g_cubeColor[7][2]);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glVertex3f(1.0f, 1.0f, -1.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glEnd();
    glPopName();
    glPushName(4 + nameId);

    ++currentCubeColors;
    i = *currentCubeColors;
    glBegin(GL_TRIANGLE_STRIP);
    glNormal3f(0.0f, 1.0f, 0.0f);
    glColor3f(g_cubeColor[i][0], g_cubeColor[i][1], g_cubeColor[i][2]);
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, -1.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glEnd();
    glBegin(GL_LINE_STRIP);
    glColor3f(g_cubeColor[7][0], g_cubeColor[7][1], g_cubeColor[7][2]);
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, -1.0f);
    glEnd();
    glPopName();
    glPushName(5 + nameId);

    ++currentCubeColors;
    i = *currentCubeColors;
    glBegin(GL_TRIANGLE_STRIP);
    glNormal3f(0.0f, -1.0f, 0.0f);
    glColor3f(g_cubeColor[i][0], g_cubeColor[i][1], g_cubeColor[i][2]);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glEnd();
    glBegin(GL_LINE_STRIP);
    glColor3f(g_cubeColor[7][0], g_cubeColor[7][1], g_cubeColor[7][2]);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glEnd();
    glPopName();
}


GLfloat gltVectorDotProduct(const GLfloat vU[], const GLfloat vV[]) {
    return vU[0] * vV[0] + vU[1] * vV[1] + vU[2] * vV[2];
}

GLfloat absf(GLfloat f) {
    return f < 0 ? -1.0f * f : f;
}

int reverseColumn(int originalColumn) {
    return BIG_CUBE_LAST_INDEX - originalColumn;
}

void getBig(int axisAndColumn[], int column, int direction, float xDot,
        float yDot, float zDot) {
    int newNumber = 0;
    char axisName[2] = { 'x', '\0' };
    int newColumn = column;
    float absX = absf(xDot);
    float absY = absf(yDot);
    float absZ = absf(zDot);

    if (absX > absY) {
        if (absX > absZ) {
            newNumber = 0;
            axisName[0] = 'x';
        } else {
            newNumber = 2;
            axisName[0] = 'z';
        }
    } else {
        if (absY > absZ) {
            newNumber = 1;
            axisName[0] = 'y';
        } else {
            newNumber = 2;
            axisName[0] = 'z';
        }
    }

    bool isReversed = false;
    switch (newNumber) {
    case CUBE_AXES_X:
        if (xDot < 0) {
            newColumn = reverseColumn(column);
            isReversed = true;
        } else {
            isReversed = false;
        }
        break;
    case CUBE_AXES_Y:
        if (yDot < 0) {
            newColumn = reverseColumn(column);
            isReversed = true;
        }
        break;
    case CUBE_AXES_Z:
        if (zDot < 0) {
            newColumn = reverseColumn(column);
            isReversed = true;
        }
        break;
    }

    int newDirection;
    newDirection = (direction == 0) ? 1 : -1;
    if (isReversed) {
        newDirection = newDirection * -1;
    }
    /* TODO: remove or make it debug code.
     sprintf( x_debugMessage, "dir, axis, dir, col, reversed %d %s %d %d %d"
     , direction, axisName, newDirection, newColumn, isReversed  );

     sprintf( x_debugMessage, "do product %d - %f %f %f"
     , newNumber
     , xDot
     , yDot
     , zDot );*/
    axisAndColumn[0] = newNumber;
    axisAndColumn[1] = newColumn;
    axisAndColumn[2] = newDirection;
}

void convertedAxis(int axisAndColumn[], int axisNumber, int column,
        int direction) {
    const GLfloat vx[] = { ThisRot.s.XX, ThisRot.s.XY, ThisRot.s.XZ };
    const GLfloat vy[] = { ThisRot.s.YX, ThisRot.s.YY, ThisRot.s.YZ };
    const GLfloat vz[] = { ThisRot.s.ZX, ThisRot.s.ZY, ThisRot.s.ZZ };

    GLfloat xDot;
    GLfloat yDot;
    GLfloat zDot;

    switch (axisNumber) {
    case 0: //x
        xDot = gltVectorDotProduct(X_AXIS_VECTOR, vx);
        yDot = gltVectorDotProduct(X_AXIS_VECTOR, vy);
        zDot = gltVectorDotProduct(X_AXIS_VECTOR, vz);
        break;
    case 1: //y
        xDot = gltVectorDotProduct(Y_AXIS_VECTOR, vx);
        yDot = gltVectorDotProduct(Y_AXIS_VECTOR, vy);
        zDot = gltVectorDotProduct(Y_AXIS_VECTOR, vz);
        break;
    case 2: //z
        xDot = gltVectorDotProduct(Z_AXIS_VECTOR, vx);
        yDot = gltVectorDotProduct(Z_AXIS_VECTOR, vy);
        zDot = gltVectorDotProduct(Z_AXIS_VECTOR, vz);
        break;
    }

    return getBig(axisAndColumn, column, direction, xDot, yDot, zDot);
}


void glutDisplay(void) {

    if (isRClicked) // If Right Mouse Clicked, Reset All Rotations
    {
        Matrix3fSetIdentity(&LastRot); // Reset Rotation
        Matrix3fSetIdentity(&ThisRot); // Reset Rotation
        Matrix4fSetRotationFromMatrix3f(&Transform, &ThisRot); // Reset Rotation
    }
    if (!isDragging) // Not Dragging
    {
        if (isClicked) // First Click
        {
            if (!g_cube_selected) {
                isDragging = true; // Prepare For Dragging
                LastRot = ThisRot; // Set Last Static Rotation To Last Dynamic One
                ArcBall.click(&MousePt); // Update Start Vector And Prepare For Dragging
            }
        }
    } else {
        if (isClicked) // Still Clicked, So Still Dragging
        {
            if (!g_cube_selected && !g_isRotated) {
                Quat4fT ThisQuat;

                ArcBall.drag(&MousePt, &ThisQuat); // Update End Vector And Get Rotation As Quaternion
                Matrix3fSetRotationFromQuat4f(&ThisRot, &ThisQuat); // Convert Quaternion Into Matrix3fT
                Matrix3fMulMatrix3f(&ThisRot, &LastRot); // Accumulate Last Rotation Into This One
                Matrix4fSetRotationFromMatrix3f(&Transform, &ThisRot); // Set Our Final Transform's Rotation From This One
            }
        } else {
            // No Longer Dragging
            isDragging = false;
        }
    }

    glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT);

    if (wireframe)
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    else
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

    glPushMatrix();

    /* translate camera to the coordinates provided by eye array */
    glTranslatef(-eye[0], -eye[1], -eye[2]);

    glMultMatrixf(Transform.M); //Apply Dynamic Transform

    int axisAndColumn[3] = { 0, 0, 1 };

    if (currentRotateOperation != NULL) {
        if (!currentRotateOperation->IsRotateBasedOnEyePoint()) {
            axisAndColumn[0] = currentRotateOperation->getAxisInteger();
            axisAndColumn[1] = currentRotateOperation->getColumn();
            axisAndColumn[2] = currentRotateOperation->getDirectionInteger();
        } else {
            convertedAxis(axisAndColumn,
                    currentRotateOperation->getAxisInteger(),
                    currentRotateOperation->getColumn(),
                    currentRotateOperation->getDirection());
            //sprintf( x_debugMessage, "do product %d %d -- %d %d ", axisAndColumn[0],axisAndColumn[1]
            //        , currentRotateOperation->getAxisInteger(), currentRotateOperation->getColumn() );
        }

        int i = 0;
        for (i = 0; i < 3; ++i) {
            if (axisAndColumn[0] == i) {
                g_rotateAxises[i] = 1.0f * axisAndColumn[2];
            } else {
                g_rotateAxises[i] = 0.0f;
            }
        }
    }

    g_cube_selected = false;
    glInitNames();
    int nameIndex = 1;

    int dAxis[3] = { 0, 0, 0 };
    for (dAxis[0] = 0; dAxis[0] < BIG_CUBE_SIZE; ++dAxis[0]) {
        for (dAxis[1] = 0; dAxis[1] < BIG_CUBE_SIZE; ++dAxis[1]) {
            for (dAxis[2] = 0; dAxis[2] < BIG_CUBE_SIZE; ++dAxis[2]) {
                int tx, ty, tz;
                tx = dAxis[0] * CUBE_WIDTH - BIG_CUBE_LAST_INDEX;
                ty = dAxis[1] * CUBE_WIDTH - BIG_CUBE_LAST_INDEX;
                tz = dAxis[2] * CUBE_WIDTH - BIG_CUBE_LAST_INDEX;
                glPushMatrix();
                if (currentRotateOperation != NULL) {
                    if (dAxis[axisAndColumn[0]] == axisAndColumn[1]) {
                        glRotatef(rquad, g_rotateAxises[0], g_rotateAxises[1], g_rotateAxises[2]);
                    }
                }
                glTranslatef(tx, ty, tz);
                // cube is selected?
                if (g_selectedCubeIndex >= nameIndex && g_selectedCubeIndex
                        < nameIndex + CUBE_FACE_SIZE) {
                    if (!g_cube_selected)
                        g_cube_selected = true;
                    // if selected, set 4 direction operation.
                    drawArrow(g_selectedCubeIndex - nameIndex, dAxis[0],
                            dAxis[1], dAxis[2]);
                }

                // To move to OpenGL, z axis is reversed about rotation Z axis from designing for DirectX.
                // don't draw inner small cube.
                Cube* cube = g_bigCube->getCube(dAxis[0], dAxis[1],
                        reverseColumn(dAxis[2]));
                if (cube != NULL)
                    drawOneCube(cube, nameIndex);
                nameIndex += CUBE_FACE_SIZE;
                glPopMatrix();
            }
        }
    }
    // update rotation.
    if (g_isRotated) {
        if (rquad < 90.0f) {
            rquad += rotate_speed;
        }
    }

    bool isPerfect = false;
    if (rquad >= 90.0f) {
        // is end of rotation? if then, finish rotation, whole cube state are supposed to be changed as same state of being displayed.
        if (g_isRotated) {
            //++x_rotateTimes;
            //sprintf( x_debugMessage, "rotate cube %d %f", x_rotateTimes, rquad );
            // axisAndColumn has 3 items, first is direction can be 1 or -1,
            // second is the column can be 0,1,2,..., third is the axis each x,y,z are 0,1,2.
            switch (axisAndColumn[0]) {
            case CUBE_AXES_X:
                g_bigCube->rotateX(axisAndColumn[1],
                        axisAndColumn[2] == 1 ? CUBE_ROTARY_DIRECTION_NEGATIVE
                                : CUBE_ROTARY_DIRECTION_POSITIVE);
                break;
            case CUBE_AXES_Y:
                g_bigCube->rotateY(axisAndColumn[1],
                        axisAndColumn[2] == 1 ? CUBE_ROTARY_DIRECTION_NEGATIVE
                                : CUBE_ROTARY_DIRECTION_POSITIVE);
                break;
                // To move to OpenGL, z axis is reversed about rotation Z axis from designing for DirectX.
            case CUBE_AXES_Z:
                g_bigCube->rotateZ(reverseColumn(axisAndColumn[1]),
                        axisAndColumn[2] == 1 ? CUBE_ROTARY_DIRECTION_POSITIVE
                                : CUBE_ROTARY_DIRECTION_NEGATIVE);
                break;
            default:
                break;
            }

            // if there are more rotations, do rotation, else reset rotation variables.
            if (!cubeRotateOperationStack.empty()) {
                performRotation();
            } else {
                //delete currentRotateOperation;
                g_isRotated = false;
                rquad = 0.0f;
                if (g_playTime == -1) {
                    // start
                    gettimeofday(&g_startTime, NULL);
                    g_playTime = 1;
                }
                isPerfect = g_bigCube->isPerfectCube();
            }
        }
    }
    unsigned int sec, millis;
    if (g_playTime == 1) {
        struct timeval lastTime;
        gettimeofday(&lastTime, NULL);
        millis = lastTime.tv_usec - g_startTime.tv_usec;
        sec = lastTime.tv_sec - g_startTime.tv_sec;
        if (millis < 0) {
            millis += 1000000;
            --sec;
        }

        if (!isPerfect) {
            sprintf(x_debugMessage, "PLAYING TIME: %4u:%02u", sec,
                    (unsigned int) (millis / 10000));
        } else {
            g_playTime = 2;
        }
    }
    if (g_playTime == 2) {
        sprintf(x_debugMessage, "PLAY TIME: %4u:%02u", sec,
                (unsigned int) (millis / 10000));
    }

    glPopMatrix();

    CalculateFrameRate();
    debugMessage(x_debugMessage);

    glFlush();
    glutSwapBuffers();

}

//------------------------------------------------------------------------
// initialise OpenGL's initial values and configurations.
//------------------------------------------------------------------------
void InitializeOGL() {
    glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);
    glShadeModel(GL_SMOOTH);
    glEnable(GL_DEPTH_TEST);

    // enable texture and binds a texture to the first element of the array
    glEnable(GL_TEXTURE_2D);
    //CreateTexture(g_TexturesArray,"logo.tga",0);

    glEnable(GL_CULL_FACE );
    glEnable(GL_POINT_SMOOTH); // anti-aliasing at vertax.
    glEnable(GL_LINE_SMOOTH);
    glHint(GL_LINE_SMOOTH, GL_NICEST);
    glLineWidth(5.0f);
    return;
}

//------------------------------------------------------------------------
//   It's the main application function. Note the clean code you can
//   obtain using he GLUT library. No calls to dark windows API
//   functions with many obscure parameters list. =)
//------------------------------------------------------------------------
int main(int argc, char** argv) {
    /*
     Glut's initialization code. Set window's size and type of display.
     Window size is put half the 800x600 resolution as defined by above
     constants. The windows is positioned at the top leftmost area of
     the screen.
     */
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA | GLUT_MULTISAMPLE);
    glutInitWindowPosition(GL_WIN_INITIAL_X, GL_WIN_INITIAL_Y);
    glutInitWindowSize(GL_WIN_WIDTH, GL_WIN_HEIGHT);
    glutInit(&argc, argv);

    glutCreateWindow("glutbasecode");

    /*
     The function below are called when the respective event
     is triggered. Very simple, isn't it?
     */
    glutReshapeFunc(glutResize); // called every time  the screen is resized
    glutDisplayFunc(glutDisplay); // called when window needs to be redisplayed
    glutIdleFunc(glutDisplay); // called whenever the application is idle
    glutKeyboardFunc(glutKeyboard); // called when the application receives a input from the keyboard
    glutMouseFunc(glutMouse); // called when the application receives a input from the mouse
    glutMotionFunc(glutMotion); // called when the mouse moves over the screen with one of this button pressed
    glutSpecialFunc(glutSpecial); // called when a special key is pressed like SHIFT

    /*
     Do any lighting, material, alpha, etc initialization or
     configuration here.
	 */
	InitializeOGL();
	gettimeofday(&g_startTime, NULL);

	/*
	 Creates a menu attached to the mouses middle button
	 the menu here consists in two options, one to toggle
	 fullscreen mode and another for exiting the application.
	 */
	glutCreateMenu(glutMenu);
	glutAddMenuEntry("Full Screen", 1);
	glutAddMenuEntry("Exit", 2);
	glutAttachMenu(GLUT_MIDDLE_BUTTON);

	/*
	 Application's main loop. All the above functions
	 are called whe the respective events are triggered
	 */
	glutMainLoop();

	return 0;
}
